package org.zjvis.datascience.service;

import lombok.Getter;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.zjvis.datascience.common.constant.UserConstant;
import org.zjvis.datascience.common.dto.user.UserDTO;
import org.zjvis.datascience.common.dto.user.UserLoginDTO;
import org.zjvis.datascience.common.exception.BaseErrorCode;
import org.zjvis.datascience.common.exception.DataScienceException;
import org.zjvis.datascience.common.shiro.UsernamePasswordCaptchaToken;
import org.zjvis.datascience.common.util.DozerUtil;
import org.zjvis.datascience.common.util.JwtUtil;
import org.zjvis.datascience.common.util.RedisUtil;
import org.zjvis.datascience.common.vo.user.UserLoginVO;
import org.zjvis.datascience.common.vo.user.UserTokenVO;
import org.zjvis.datascience.common.vo.user.UserVO;
import org.zjvis.datascience.service.mapper.UserMapper;

import javax.security.auth.login.LoginException;
import java.util.Set;

/**
 * @description Login 登录 Service
 * @date 2021-10-18
 */
@Service
@Getter
public class LoginService {
    private final static Logger logger = LoggerFactory.getLogger("LoginService");

    @Value("${spring.profiles.active}")
    private String profileActive;

    @Autowired
    private UserService userService;
    @Autowired
    private PasswordService passwordService;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RedisUtil redisUtil;

    public UserTokenVO login(UserLoginVO userVO) throws Exception {
        String password = passwordService.decryptPassword(userVO.getPassword());
        UserDTO userRecord = userMapper.findByKey(userVO.getName());
        if(userRecord == null) {
            throw new DataScienceException(BaseErrorCode.USER_USERNAME_OR_PASSWORD_ERROR);
        }
        checkLoginAuth(userRecord.getName()); // 校验账户是否能登录
        UsernamePasswordCaptchaToken userToken = new UsernamePasswordCaptchaToken(userRecord.getName(), password);
        // 目前全部记住密码，使用cookie来记录token
        userToken.setRememberMe(true);

        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(userToken);
            UserTokenVO userTokenVO = getInfo();

            String token = JwtUtil.sign(userTokenVO.getUser().getName(),userTokenVO.getUser().getId());
            userTokenVO.setToken(token);
            return userTokenVO;
        } catch (AuthenticationException e) {
            recordLoginTimes(userRecord.getName());
            logger.warn("login error: user name or password error ");
            throw new LoginException(BaseErrorCode.USER_USERNAME_OR_PASSWORD_ERROR.getMsg());
        }
    }

    public UserTokenVO getInfo() {
        UserDTO userDto = JwtUtil.getCurrentUserDTO();
        Set<String> permissions = userService.queryPermissionByUserId(userDto.getId());
        return UserTokenVO.builder()
                .permission(permissions)
                .user(DozerUtil.mapper(userDto, UserVO.class))
                .token(JwtUtil.getCurrentUserToken())
                .build();
    }

    public void logout() {
        Subject subject = SecurityUtils.getSubject();
        JwtUtil.logout();
        subject.logout();
    }

    private void checkLoginAuth(String name) {
        long time = getTime();
        String key = UserConstant.LOGIN_TIMES_PREFIX + name;
        UserLoginDTO value = (UserLoginDTO) redisUtil.get(key);
        if(value != null && value.getLoginErrorTimes() >= UserConstant.COUNT_LOGIN_FAIL) {
            throw new DataScienceException(BaseErrorCode.USER_LOGIN_TIMES_EXCEEDED);
        } else if(value != null && value.getLast5MinErrorTimes() >= UserConstant.COUNT_LOGIN_FAIL_IN_5MIN) {
            if(time - value.getLastLoginTime() <= UserConstant.USER_LOCKED_TIME)
                throw new DataScienceException(BaseErrorCode.USER_USERNAME_IS_LOCKED);
        }
    }

    /**
     * 记录用户尝试登录次数
     */
    private void recordLoginTimes(String name) {
        long time = getTime();
        String key = UserConstant.LOGIN_TIMES_PREFIX + name;
        UserLoginDTO value;
        if(redisUtil.get(key) == null) { // 24h内第一次登录失败
            value = new UserLoginDTO();
            value.setLoginErrorTimes(1);
            value.setLast5MinErrorTimes(1);
            value.setLast5MinTime(time);
            value.setLastLoginTime(time);
            redisUtil.set(key, value, UserConstant.RECORD_EFFECTIVE_TIME);
        } else { // 不是第一次登录失败
            value = (UserLoginDTO) redisUtil.get(key);

            value.setLastLoginTime(time);
            int times = value.getLoginErrorTimes();
            value.setLoginErrorTimes(times + 1);

            if(time - value.getLast5MinTime() >= UserConstant.EFFECTIVE_TIME) { // 过了上一个5min，重新开始计数
                value.setLast5MinErrorTimes(1);
                value.setLast5MinTime(time);
            } else { // 如果在上一个5min内，则叠加
                times = value.getLast5MinErrorTimes();
                value.setLast5MinErrorTimes(times + 1);
            }

            long expire = redisUtil.getExpire(key);
            redisUtil.set(key, value, expire);
        }
    }

    /**
     * @Function: 获取当前时间
     */
    private long getTime() {
        return System.currentTimeMillis()/1000;
    }

}
